{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "编写线程安全的类不是难事，用同步原语（synchronization primitives）保护内部状态即可。\n",
    "\n",
    "但是对象的生与死不能由对象自身拥有的mutex（互斥器）来保护。如何避免对象析构时可能存在的race condition（竞态条件）>是C++多线程编程面临的基本问题，可以借助Boost库中的`shared_ptr`和`weak_ptr`完美解决。这也是实现线程安全的*Observer模式*的必备技术。\n",
    "\n",
    "本章源自 2009年12月我在上海祝成科技举办的 C++技术大会的一场演讲[《当析构函数遇到多线程》](https://blog.csdn.net/solstice/article/details/5238671)，读者应具有C++多线程编程经验，熟悉互斥器、竞态条件等概念，了解智能指针，知道Observer设计模式。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 1.1 当析构函数遇到多线程\n",
    "\n",
    "与其他面向对象语言不同，C++要求程序员自己管理对象的生命期，这在多线程环境下显得尤为困难。\n",
    "\n",
    "当一个对象能被多个线程同时看到时，那么对象的销毁时机就会变得模糊不清，可能出现多种竞态条件（race condition ）:\n",
    "\n",
    "- 在即将析构一个对象时，从何而知此刻是否有别的线程正在执行该对象的成员函数?\n",
    "- 如何保证在执行成员函数期间，对象不会在另一个线程被析构?\n",
    "- 在调用某个对象的成员函数之前，如何得知这个对象还活着?它的析构函数会不会碰巧执行到一半?\n",
    "\n",
    "解决这些 race condition 是 C++ 多线程编程面临的基本问题。本文试图以`shared_ptr`一劳永逸地解决这些问题，减轻C++多线程编程的精神负担。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 线程安全的定义\n",
    "依据[JCP]，一个线程安全的class应当满足以下三个条件∶\n",
    "\n",
    "> Brian Goetz. Java Concurrency in Practice . Addison-Wesley, 2006\n",
    "\n",
    "- 多个线程同时访问时，其表现出正确的行为。\n",
    "- 无论操作系统如何调度这些线程，无论这些线程的执行顺序如何交织(interleaving )。\n",
    "- 调用端代码无须额外的同步或其他协调动作。\n",
    "\n",
    "\n",
    "依据这个定义，C++标准库里的大多数 class都不是线程安全的，包括 `std::string`、`std::vector`、`std::map` 等，因为这些 class通常需要在外部加锁才能供多个线程同时访问。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## MutexLock 与 MutexLockGuard\n",
    "\n",
    "为了便于后文讨论，先约定两个工具类。我相信每个写C++多线程程序的人都实现过或使用过类似功能的类，代码见§2.4。\n",
    "\n",
    "\n",
    "`MutexLock `封装临界区（critical section）\n",
    "\n",
    "- 这是一个简单的资源类，用RAII手法[CCS.条款13]封装互斥器的创建与销毁。\n",
    "- 临界区在 Windows上是 `struct CRITICAL_SECTION`，是可重入的; 在Linux下是 `pthread_mutex_t`，默认是不可重入的。\n",
    "- `MutexLock` 一般是别的class的数据成员。\n",
    "\n",
    "> 可重入与不可重人的讨论见§2.11。\n",
    "\n",
    "\n",
    "`MutexLockGuard` 封装临界区的进入和退出，即加锁和解锁。\n",
    "\n",
    "- `MutexLockGuard` 一般是个栈上对象，它的作用域刚好等于临界区域。\n",
    "\n",
    "这两个class都不允许拷贝构造和赋值，它们的使用原则见§ 2.1。\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 一个线程安全的 Counter 示例\n",
    "\n",
    "编写单个的线程安全的 class 不算太难，只需用同步原语保护其内部状态。\n",
    "\n",
    "例如下面这个简单的计数器类`Counter∶`"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "// A thread-safe counter\n",
    "// In a real world,atomic operations are preferred.\n",
    "class Counter : boost::noncopyable\n",
    "{\n",
    "\t// copy-ctor and assignment should be private by default for a class\n",
    "public:\n",
    "\tCounter() : value_(0){}\n",
    "\n",
    "\tint64_t value() const;\n",
    "\tint64_t getAndIncrease();\n",
    "\n",
    "private:\n",
    "\tint64_t value_;\n",
    "\tmutable MutexLock mutex_;\n",
    "};\n",
    "\n",
    "int64_t Counter::value() const{\n",
    "    MutexLockGuard lock(mutex_); // lock的析构会晚于返回对象的构造，因此有效地保护了这个共享数据\n",
    "    return value_;\n",
    "}\n",
    "\n",
    "int64_t Counter::getAndIncrease(){\n",
    "    MutexLockGuard lock(mutex_);\n",
    "    int64_t ret = value_++\n",
    "    return ret;\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "> AI: \"lock的析构会晚于返回对象的构造，因此有效地保护了这个共享数据\":\n",
    "\n",
    "由于`MutexLockGuard`对象的析构函数会在返回对象的构造函数之后被调用，所以在`Counter`类的`value()`和`getAndIncrease()`函数中使用`MutexLockGuard`对象可以有效地保护共享数据`value_`，避免多个线程同时访问和修改`value_`导致的数据竞争和错误。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "这个class很直白，一看就明白，也容易验证它是线程安全的。每个`Counter` 对象有自己的 `mutex_`，因此不同对象之间不构成锁争用（lock contention）。即两个线程有可能同时执行L24，前提是它们访问的不是同一个 Counter 对象。\n",
    "\n",
    "注意到其`mutex_`成员是 `mutable` 的，意味着`const` 成员函数如 `Counter::value()`也能直接使用non-const 的 `mutex_`。\n",
    "\n",
    "> 思考∶如果 `mutex_`是 `static`，是否影响正确性和/或性能?\n",
    "\n",
    "\n",
    "尽管这个`Counter` 本身毫无疑问是线程安全的，但如果 `Counter` 是动态创建的并通过指针来访问，前面提到的对象销毁的 race condition 仍然存在。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "> AI: 关于将 `mutex_` 声明为 `static`\n",
    "\n",
    "1. **正确性**\n",
    "   - 当 `mutex_` 是静态成员时，所有 `Counter` 实例共享同一把锁。\n",
    "   - 共享锁仍然可以保证每次对任意 `Counter` 对象的访问都在临界区内，因此不会破坏线程安全性（数据竞态依旧被消除）。\n",
    "   - 需要注意的是，若在成员函数内部再次尝试获取同一把锁（例如在某个调用链里出现递归或嵌套调用），仍然要遵守互斥量的使用规则，否则可能产生死锁。不过这与 `static` 与否无关。\n",
    "2. **性能与扩展性**\n",
    "   - 所有实例共用一把锁意味着无论访问的是否是同一个 `Counter`，线程都会互斥执行。\n",
    "   - 一旦系统中存在多个 `Counter` 对象并被不同线程频繁访问，锁竞争会急剧上升，极大降低吞吐量，失去“每个对象互不干扰”的并发优势。\n",
    "   - 因此，将锁设为静态的最大问题不是正确性，而是扩展性和性能：它将原本可以并行的独立计数器操作强行串行化。\n",
    "3. **`mutable` 的必要性**\n",
    "   - 当 `mutex_` 是静态成员时，`const` 成员函数可以直接访问它，不再需要 `mutable`。这是因为静态成员不隶属于对象本身，`const` 限定符只作用于对象的非静态成员。\n",
    "   - 当 `mutex_` 是普通非静态成员时，若希望在 `const` 成员函数中上锁，就需要 `mutable`。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "> AI:\n",
    "\n",
    "这段话的意思是，虽然这个`Counter`类本身已经是线程安全的，但如果`Counter`是通过new动态创建的并且通过指针来访问，那么当其在某条线程中被销毁时，其他线程仍然可能会同时访问该指针，导致race condition。因此，为了避免这种情况，我们需要在销毁指针之前先对其进行加锁，保证在对象被销毁前，其他线程无法访问该指针。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 1.2 对象的创建很简单\n",
    "\n",
    "对象构造要做到线程安全，唯一的要求是在构造期间不要泄露 `this` 指针, 即\n",
    "\n",
    "- 不要在构造函数中注册任何回调;\n",
    "- 也不要在构造函数中把 `this` 传给跨线程的对象;\n",
    "- 即便在构造函数的最后一行也不行。\n",
    "\n",
    "之所以这样规定，是因为在构造函数执行期间对象还没有完成初始化，如果 `this`被泄露（escape）给了其他对象（其自身创建的子对象除外），那么别的线程有可能访问这个半成品对象，这会造成难以预料的后果。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "> AI:\n",
    "\n",
    "对象构造的线程安全不是说“构造函数里加锁”就完事，而是要满足整个生命周期的几个关键条件：构造只执行一次、构造期间不对外泄露、构造完成后再安全发布，并提供足够的同步保障其他线程看到的总是“一致、完整”的对象状态。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "class Observer : boost::noncopyable\n",
    "{\n",
    "public:\n",
    "\tvirtual ~Observer();\n",
    "\tvirtual void update() = 0;\n",
    "};\n",
    "\n",
    "class Observable : boost::noncopyable\n",
    "{\n",
    "public:\n",
    "\tvoid register_(Observer* x);\n",
    "\tvoid unregister(Observer* x);\n",
    "\n",
    "\tvoid notifyObervers(){\n",
    "        for(Observer* x : observers_){\n",
    "            x->update(); // (3)\n",
    "        }\n",
    "  }\n",
    "\n",
    "private:\n",
    "\tstd::vector<Observer*> observers_;\n",
    "};"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "// Don't do this\n",
    "\n",
    "class Foo ∶ public Observer // Observer 的定义见第 10 页\n",
    "{\n",
    "public:\n",
    "    Foo(Observable* s)\n",
    "    {\n",
    "        s->register_（this）; //错误，非线程安全\n",
    "    }\n",
    "    virtual void update();\n",
    "};"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "// Do this.\n",
    "\n",
    "class Foo : public Observer\n",
    "{\n",
    "public:\n",
    "    Foo();\n",
    "    virtual void update();\n",
    "    // 另外定义一个函数，在构造之后执行回调函数的注册工作\n",
    "    void observe(Observable* s)\n",
    "    {\n",
    "        s->register_(this);\n",
    "    }\n",
    "};\n",
    "\n",
    "Foo* pFoo = new Foo;\n",
    "Observable* s = getSubject();\n",
    "pFoo->observe(s); // 二段式构造，或者直接写 s->register_(pFoo);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "这也说明，**二段式构造**——即构造函数 ＋`initialize()`——有时会是好办法，这虽然不符合C++教条，但是多线程下别无选择。另外，既然允许二段式构造，那么构造函数不必主动抛异常，调用方靠 `initialize()`的返回值来判断对象是否构造成功，这能简化错误处理。\n",
    "\n",
    "即使构造函数的最后一行也不要泄露 `this`，因为`Foo`有可能是个基类，基类先于派生类构造，执行完 `Foo::Foo()`的最后一行代码还会继续执行派生类的构造函数，这时 most-derived class的对象还处于构造中，仍然不安全。\n",
    "\n",
    "相对来说，对象的构造做到线程安全还是比较容易的，毕竟曝光少，回头率为零。而析构的线程安全就不那么简单，这也是本章关注的焦点。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 1.3 销毁太难\n",
    "\n",
    "对象析构，这在单线程里不构成问题，最多需要注意避免空悬指针和野指针。而在多线程程序中，存在了太多的竞态条件。对一般成员函数而言，做到线程安全的办法是让它们顺次执行，而不要并发执行（关键是不要同时读写共享状态）。\n",
    "\n",
    "也就是让每个成员函数的临界区不重叠。这是显而易见的，不过有一个隐含条件或许不是每个人都能立刻想到∶成员函数用来保护临界区的互斥器本身必须是有效的。而析构函数破坏了这一假设，它会把 `mutex `成员变量销毁掉。悲剧啊!\n",
    "\n",
    "> 空悬指针（dangling pointer）指向已经销毁的对象或已经回收的地址; 野指针（wild pointer）指的是未经初始化的指针"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## `mutex`不是办法\n",
    "\n",
    "`mutex`只能保证一个函数一个接一个地执行, 考虑以下代码, 它试图互斥锁来保护析构函数\n",
    "\n",
    "```asciidoc\n",
    "+----------------------------------------+----------------------------------------+\n",
    "| Foo::~Foo()                            | void Foo::update()                     |\n",
    "| {                                      | {                                      |\n",
    "|   MutexLockGuard lock(mutex_);         |   MutexLockGuard lock(mutex_);  // (2) |\n",
    "|   // free internal state  (1)          |   // make use of internal state        |\n",
    "| }                                      | }                                      |\n",
    "+----------------------------------------+----------------------------------------+\n",
    "```\n",
    "\n",
    "时, 有A, B两个线程都能看到`Foo`对象`x`, 线程A即将销毁`x`,而线程B正准备调用`x->update()`\n",
    "\n",
    "```asciidoc\n",
    "extern Foo* x;  // visible by all threads\n",
    "\n",
    "+----------------------------------------+----------------------------------------+\n",
    "| // thread A                            | // thread B                            |\n",
    "| delete x;                              | if (x) {                               |\n",
    "| x = NULL;  // helpless                 |   x->update();                         |\n",
    "|                                        | }                                      |\n",
    "+----------------------------------------+----------------------------------------+\n",
    "```\n",
    "\n",
    "尽管线程 A在销毁对象之后把指针置为了`NULL`，尽管线程 B在调用`x`的成员函数之前检查了指针`x`的值，但还是无法避免一种 race condition∶\n",
    "- 线程A执行到了析构函数的(1)处，已经持有了互斥锁，即将继续往下执行。\n",
    "- 线程B通过了`if(x)`检测，阻塞在(2)处。\n",
    "\n",
    "接下来会发生什么，只有天晓得。因为析构函数会把 `mutex` 销毁。那么（2）处有可能永远阻塞下去，有可能进入\"临界区\"，然后core dump，或者发生其他更糟糕的情况。\n",
    "\n",
    "这个例子至少说明`delete`对象之后把指针置为`NULL`根本没用，如果一个程序要靠这个来防止二次释放，说明代码逻辑出了问题。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 作为数据成员的 mutex 不能保护析构\n",
    "\n",
    "前面的例子说明，作为 class 数据成员的 `MutexLock` 只能用于同步本 class 的其他数据成员的读和写，它不能保护安全地析构。\n",
    "\n",
    "因为 `MutexLock` 成员的生命期最多与对象一样长，而析构动作可说是发生在对象身故之后（或者身亡之时）。\n",
    "\n",
    "另外，对于基类对象，那么调用到基类析构函数的时候，派生类对象的那部分已经析构了，那么基类对象拥有的 `MutexLock` 不能保护整个析构过程。\n",
    "\n",
    "再说，析构过程本来也不需要保护，因为只有别的线程都访问不到这个对象时，析构才是安全的，否则会有§1.1 谈到的竞态条件发生。\n",
    "\n",
    "\n",
    "另外如果要同时读写一个 class 的两个对象，有潜在的死锁可能。比方说有`swap()`这个函数∶"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "// 一旦 A 获得 a.mutex_ 而 B 获得 b.mutex_，它们会互相等待对方释放另一把锁，最终陷入死锁。\n",
    "\n",
    "void swap(Counter& a,Counter& b)\n",
    "{\n",
    "    MutexLockGuard aLock(a.mutex_); // potential dead lock\n",
    "    MutexLockGuard bLock(b.mutex_); \n",
    "    int64_t value = a.value_;\n",
    "    a.value_= b.value_; \n",
    "    b.value__= value;\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "如果线程 A执行`swap(a，b);`而同时线程B执行`swap(b，a);`，就有可能死锁。\n",
    "\n",
    "`operator=()`也是类似的道理。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "// 当 lhs = rhs 与 rhs = lhs 在不同线程中并发执行时，锁顺序也可能互相颠倒，从而死锁。\n",
    "\n",
    "Counter& Counter::operator=(const Counter)\n",
    "{\n",
    "    if(this == &rhs)\n",
    "        return *this;\n",
    "\n",
    "    MutexLockGuard myLock(mutex_); // potential dead lock\n",
    "    MutexLockGuard itsLock(rhs.mutex_);\n",
    "    value_ = rhs.value_; // 改成value_ = rhs.value()会死锁\n",
    "\n",
    "    return *this;\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "一个函数如果要锁住相同类型的多个对象，为了保证始终按相同的顺序加锁，我们可以比较 `mutex` 对象的地址，始终先加锁地址较小的 `mutex`。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "> 现代C++\n",
    "\n",
    "| 场景          | 推荐做法                                                     |\n",
    "| ------------- | ------------------------------------------------------------ |\n",
    "| C++11/14 项目 | 使用 `std::lock()` 获取所有锁，再配合 `std::lock_guard` + `std::adopt_lock`。 |\n",
    "| C++17 及更新  | 直接使用 `std::scoped_lock`。它内部调用了 `std::lock` 并提供 RAII。 |"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 1.4 线程安全的 Observer 有多难\n",
    "\n",
    "一个动态创建的对象是否还活着，光看指针是看不出来的（引用也一样看不出来）。指针就是指向了一块内存，这块内存上的对象如果已经销毁，那么就根本不能访问[CCS，条款99]（就像 `free(3)`之后的地址不能访问一样），既然不能访问又如何知道对象的状态呢? 换句话说，判断一个指针是不是合法指针没有高效的办法，这是C/C++指针问题的根源。（万一原址又创建了一个新的对象呢?再万一这个新的对象的类型异于老的对象呢?）\n",
    "\n",
    ">  Java 中，一个 reference 只要不为null，它一定指向有效的对象。\n",
    "\n",
    "在面向对象程序设计中，对象的关系主要有三种∶composition、aggregation、association"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "> AI:\n",
    "\n",
    "- Composition(组合关系) 表示一种强的组合关系，指一个对象是由另一个或一些其他对象组合而成的整体，例如，一个汽车由引擎、车轮、底盘等组合而成。在 composition 中，组合对象是不能独立存在的，一旦组合对象不存在了，那么整个对象也就不存在了。\n",
    "- Aggregation(聚合关系) 表示一种弱的组合关系，指一个对象包含多个其他对象，但是这些对象可以独立存在，例如，一个学校包含多个班级，每个班级可以独立存在。在 aggregation 中，聚合对象是可以独立存在的，即使它们被删除了，整个对象也不会消失。\n",
    "- Association(关联关系) 表示一种对象之间的关联关系，指对象之间存在某种关系，例如，一个学生与他的课程之间就存在着关联关系。在 association 中，关联对象是独立的，它们可以独立存在，但是它们之间存在着某种关联关系。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "V8 engine starting...\n",
      "Car is driving.\n"
     ]
    }
   ],
   "source": [
    "// 组合（Composition）, 生命周期强依赖：部件对象随着整体对象的创建和销毁而创建和销毁\n",
    "\n",
    "#include <iostream>\n",
    "#include <string>\n",
    "\n",
    "class Engine {\n",
    "public:\n",
    "    explicit Engine(const std::string& model) : model_(model) {}\n",
    "\n",
    "    void start() const {\n",
    "        std::cout << model_ << \" engine starting...\\n\";\n",
    "    }\n",
    "\n",
    "private:\n",
    "    std::string model_;\n",
    "};\n",
    "\n",
    "class Car {\n",
    "public:\n",
    "    explicit Car(const std::string& engineModel) : engine_(engineModel) {}  // Car 直接拥有 Engine\n",
    "\n",
    "    void drive() const {\n",
    "        engine_.start();\n",
    "        std::cout << \"Car is driving.\\n\";\n",
    "    }\n",
    "\n",
    "private:\n",
    "    Engine engine_;  // 组合：Car 完全控制 Engine 的生命周期\n",
    "};\n",
    "\n",
    "{\n",
    "    Car car(\"V8\");\n",
    "    car.drive();\n",
    "}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Team Warriors roster:\n",
      " - Alice\n",
      " - Bob\n"
     ]
    }
   ],
   "source": [
    "//  聚合（Aggregation）,生命周期弱依赖：整体对象引用部件对象，但部件对象可以独立存在。\n",
    "\n",
    "#include <iostream>\n",
    "#include <string>\n",
    "#include <vector>\n",
    "\n",
    "class Player {\n",
    "public:\n",
    "    explicit Player(const std::string& name) : name_(name) {}\n",
    "\n",
    "    const std::string& name() const {\n",
    "        return name_;\n",
    "    }\n",
    "\n",
    "private:\n",
    "    std::string name_;\n",
    "};\n",
    "\n",
    "class Team {\n",
    "public:\n",
    "    explicit Team(const std::string& teamName) : teamName_(teamName) {}\n",
    "\n",
    "    void addPlayer(Player* player) {  // 聚合：Team 仅持有指针，不负责 Player 的生命周期\n",
    "        players_.push_back(player);\n",
    "    }\n",
    "\n",
    "    void showRoster() const {\n",
    "        std::cout << \"Team \" << teamName_ << \" roster:\\n\";\n",
    "        for (const auto* player : players_) {\n",
    "            std::cout << \" - \" << player->name() << '\\n';\n",
    "        }\n",
    "    }\n",
    "\n",
    "private:\n",
    "    std::string teamName_;\n",
    "    std::vector<Player*> players_;  // 使用原始指针强调 Team 不拥有 Player\n",
    "};\n",
    "\n",
    "{\n",
    "    Player alice(\"Alice\");\n",
    "    Player bob(\"Bob\");\n",
    "\n",
    "    Team team(\"Warriors\");\n",
    "    team.addPlayer(&alice);\n",
    "    team.addPlayer(&bob);\n",
    "\n",
    "    team.showRoster();\n",
    "}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Mr. Zhang is teaching Li Hua\n"
     ]
    }
   ],
   "source": [
    "// 关联（Association）, 普通引用关系：对象之间知道彼此，可以相互调用方法，但没有所有权或生命周期上的依赖\n",
    "\n",
    "#include <iostream>\n",
    "#include <string>\n",
    "\n",
    "class Student {\n",
    "public:\n",
    "    explicit Student(const std::string& name) : name_(name) {}\n",
    "\n",
    "    const std::string& name() const {\n",
    "        return name_;\n",
    "    }\n",
    "\n",
    "private:\n",
    "    std::string name_;\n",
    "};\n",
    "\n",
    "class Teacher {\n",
    "public:\n",
    "    explicit Teacher(const std::string& name) : name_(name) {}\n",
    "\n",
    "    void teach(const Student& student) const {\n",
    "        std::cout << name_ << \" is teaching \" << student.name() << '\\n';\n",
    "    }\n",
    "\n",
    "private:\n",
    "    std::string name_;\n",
    "};\n",
    "\n",
    "{\n",
    "    Teacher teacher(\"Mr. Zhang\");\n",
    "    Student student(\"Li Hua\");\n",
    "\n",
    "    teacher.teach(student);  // 关联：Teacher 与 Student 互相知道对方\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "composition（组合/复合）关系在多线程里不会遇到什么麻烦，因为对象`x`的生命期由其唯一的拥有者 `owner `控制，`owner` 析构的时候会把x也析构掉。从形式上看，`x`是 `owne`r 的直接数据成员，或者`scoped_ptr`成员，抑或 `owner`持有的容器的元素。\n",
    "\n",
    "后两种关系在C++ 里比较难办，处理不好就会造成内存泄漏或重复释放。\n",
    "- association（关联/联系）是一种很宽泛的关系，它表示一个对象 `a`用到了另一个对象`b`，调用了后者的成员函数。从代码形式上看，`a`持有`b`的指针（或引用），但是`b`的生命期不由 `a`单独控制。\n",
    "- aggregation（聚合）关系从形式上看与 association 相同，除了`a`和`b`有逻辑上的整体与部分关系。如果`b`是动态创建的并在整个程序结束前有可能被释放，那么就会出现§1.1谈到的竞态条件。\n",
    "\n",
    "\n",
    "那么似乎一个简单的解决办法是∶只创建不销毁。程序使用一个对象池来暂存用过的对象，下次申请新对象时，如果对象池里有存货，就重复利用现有的对象，否则就新建一个。对象用完了，不是直接释放掉，而是放回池子里。这个办法当然有其自身的很多缺点，但至少能避免访问失效对象的情况发生。\n",
    "\n",
    "这种山寨办法的问题有∶\n",
    "- 对象池的线程安全，如何安全地、完整地把对象放回池子里，防止出现\"部分放回\"的竞态?（线程A认为对象`x`已经放回了，线程 B认为对象`x`还活着。）\n",
    "- 全局共享数据引发的lock contention(锁竞争)，这个集中化的对象池会不会把多线程并发的操作串行化?\n",
    "- 如果共享对象的类型不止一种，那么是重复实现对象池还是使用类模板?\n",
    "- 会不会造成内存泄漏与分片?因为对象池占用的内存只增不减，而且多个对象池不能共享内存（想想为何）。\n",
    "\n",
    "回到正题上来，如果对象`x`注册了任何非静态成员函数回调，那么必然在某处持有了指向`x`的指针，这就暴露在了race condition之下。\n",
    "\n",
    "一个典型的场景是 Observer模式\n",
    "\n",
    "- [Observer](../../recipes/thread/test/Observer.cc)\n",
    "\n",
    "[当 `Observable`通知每一个 `Observer` 时](../../recipes/thread/test/Observer.cc#L31), 它从何得知 `Observer` 对象`x`还活着? 要不试试在 `Observer` 的析构函数里调用[`unregister()`](../../recipes/thread/test/Observer.cc#L42),来解注册? 恐难奏效。\n",
    "\n",
    "\n",
    "我们试着让 `Observer` 的析构函数去调用 `unregister(this)`，这里有两个race conditions。\n",
    "- 其一∶[`subject_->unregister(this)`](../../recipes/thread/test/Observer.cc#L42)如何得知 `subject_`还活着\n",
    "- 其二∶就算`subject_`指向某个永久存在的对象，那么还是险象环生∶\n",
    "- 线程A执行到[`subject_->unregister(this)`](../../recipes/thread/test/Observer.cc#L42)之前，还没有来得及`unregister`本对象。\n",
    "- 线程B执行到[`x->update();`](../../recipes/thread/test/Observer.cc#L31), `x`正好指向是[`subject_->unregister(this)`](../../recipes/thread/test/Observer.cc#L42)正在析构的对象。\n",
    "\n",
    "这时悲剧又发生了，既然`x`所指的 `Observer` 对象正在析构，调用它的任何非静态成员函数都是不安全的，何况是虚函数。更糟糕的是，`Observer`是个基类，执行到 [`subject_->unregister(this)`](../../recipes/thread/test/Observer.cc#L42) 时，派生类对象已经析构掉了，这时候整个对象处于将死未死的状态，coredump恐怕是最幸运的结果。\n",
    "\n",
    "> C++标准对在构造函数和析构函数中调用虚函数的行为有明确规定，但是没有考虑并发调用的情况。\n",
    "\n",
    "这些 race condition 似乎可以通过加锁来解决，但在哪儿加锁，谁持有这些互斥锁，又似乎不是那么显而易见的。要是有什么活着的对象能帮帮我们就好了，它提供一个`isAlive()`类的程序函数，告诉我们那个对象还在不在。可惜指针和引用都不是对象，它们是内建类型。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 1.5 原始指针有何不妥\n",
    "\n",
    "指向对象的原始指针（raw pointer）是坏的，尤其当暴露给别的线程时。`Observable` 应当保存的不是原始的`Observer*`，而是别的什么东西，能分辨 `Observer` 对象是否存活。类似地，如果 `Observer` 要在析构函数里解注册（这虽然不能解决前面提到的race condition，但是在析构函数里打扫战场还是应该的），那么 `subject_`的类型也不能是原始的`Observable*`。\n",
    "\n",
    "有经验的C++程序员或许会想到用智能指针。没错，这是正道，但也没那么简单，有些关窍需要注意。这两处直接使用`shared_ptr`是不行的，会形成循环引用，直接造成资源泄漏。别着急，后文会一一讲到。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 空悬指针\n",
    "\n",
    "有两个指针 `p1` 和 `p2`，指向堆上的同一个对象`Object`，`p1`和 `p2`位于不同的线程中（图1-1的左图）。\n",
    "\n",
    "假设线程 A 通过`p1`指针将对象销毁了（尽管把 `p1`置为了`NULL`），那 `p2` 就成了空悬指针（图1-1的右图）。这是一种典型的C/C++内存错误。\n",
    "\n",
    "<img src=\"./images/1.1.png\" alt=\"1.1\" style=\"zoom:50%;\" />\n",
    "\n",
    "要想安全地销毁对象，最好在别人（线程）都看不到的情况下，偷偷地做。​（这正是垃圾回收的原理，所有人都用不到的东西一定是垃圾。​）"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 一个\"解决办法\"\n",
    "\n",
    "一个解决空悬指针的办法是，引入一层间接性，让`p1`和 `p2`所指的对象永久有效。比如图1-2中的 `proxy` 对象，这个对象，持有一个指向`Object`的指针。（从C语言的角度，`p1` 和 `p2`都是二级指针。）\n",
    "\n",
    "<img src=\"./images/1.2.png\" alt=\"1.2\" style=\"zoom:50%;\" />\n",
    "\n",
    "当销毁`Object`之后，`proxy `对象继续存在，其值变为`0`（见图1-3）。而p2也没有变成空悬指针，它可以通过查看 `proxy` 的内容来判断 `Object` 是否还活着。\n",
    "\n",
    "<img src=\"./images/1.3.png\" alt=\"1.3\" style=\"zoom:50%;\" />\n",
    "\n",
    "要线程安全地释放`Object`也不是那么容易，race condition 依旧存在。比如 `p2`看第一眼的时候 `proxy` 不是零，正准备去调用`Object` 的成员函数，期间对象已经被`p1`给销毁了。\n",
    "\n",
    "问题在于，何时释放 `proxy `指针呢?"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "##  一个更好的解决办法\n",
    "\n",
    "为了安全地释放 `proxy`，我们可以引入引用计数（reference counting），再把`p1`和`p2`都从指针变成对象`sp1`和`sp2`。`proxy`现在有两个成员，指针和计数器。\n",
    "\n",
    "一开始，有两个引用，计数值为`2`\n",
    "\n",
    "<img src=\"./images/1.4.png\" alt=\"1.4\" style=\"zoom:50%;\" />\n",
    "\n",
    "`sp1`析构了, 引用计数的值减为`1`\n",
    "\n",
    "<img src=\"./images/1.5.png\" alt=\"1.5\" style=\"zoom:50%;\" />\n",
    "\n",
    "`sp2`也析构了，引用计数降为`0`，可以安全地销毁 p`roxy` 和O`bject`了\n",
    "\n",
    "<img src=\"./images/1.6.png\" alt=\"1.6\" style=\"zoom:50%;\" />\n",
    "\n",
    "慢着!这不正是引用计数型智能指针吗?"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 一个万能的解决方案\n",
    "\n",
    "[引入另外一层间接性（another layer of indirection）](https://en.wikipedia.org/wiki/Abstraction_layer)，用对象来管理共享资源（如果把 `Object`看作资源的话），亦即 handle/body 惯用技法（idiom）。当然，编写线程安全、高效的引用计数 handle 的难度非凡，作为一名[谦卑的程序员](https://www.cs.utexas.edu/~EWD/transcriptions/EWD03xx/EWD340.html)，用现成的库就行。万幸，C++的 TR1标准库里提供了一对\"神兵利器\"，可助我们完美解决这个头疼的问题。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "> AI: Handle/Body 惯用技法（Handle–Body Idiom）\n",
    "\n",
    "也称为 **Pimpl（Pointer to Implementation）惯用法**、**Cheshire Cat 惯用法**。它通过将对象的接口与实现分离，把内部细节隐藏在“主体”（Body）中，仅通过“句柄”（Handle）对外暴露 API，从而减少编译依赖、缩短编译时间、强化封装性。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 1.6 神器 `shared_ptr/weak_ptr`\n",
    "\n",
    "`shared_ptr`是引用计数型智能指针，在 Boost 和 `std::tr1`里均提供，也被纳入C++11标准库，现代主流的C++编译器都能很好地支持。`shared_ptr<T>`是一个类模板（class template），它只有一个类型参数，使用起来很方便。引用计数是自动化资源管理的常用手法，当引用计数降为`0`时，对象（资源）即被销毁。`weak_ptr`也是一个引用计数型智能指针，但是它不增加对象的引用次数，即弱（weak）引用。\n",
    "\n",
    "`shared_ptr `的基本用法和语意请参考手册或教程，本书从略。谈几个关键点。\n",
    "- `shared_ptr` 控制对象的生命期。`shared_ptr` 是强引用（想象成用铁丝绑住堆上的对象），只要有一个指向`x`对象的 shared_ptr 存在，该`x`对象就不会析构。当指向对象`x`的最后一个 `shared_ptr` 析构或 `reset()`的时候，`x`保证会被销毁。\n",
    "- `weak_ptr` 不控制对象的生命期，但是它知道对象是否还活着（想象成用棉线轻轻拴住堆上的对象）。如果对象还活着，那么它可以提升（promote）为有效的`shared_ptr;` 如果对象已经死了，提升会失败，返回一个空的 `shared_ptr`。\"提升/lock()\"行为是线程安全的。\n",
    "- `shared_ptr/weak_ptr`的\"计数\"在主流平台上是原子操作，没有用锁，性能不俗。\n",
    "- `shared_ptr/weak_ptr`的线程安全级别与 `std::string`和STL容器一样，后面还会讲。\n",
    "\n",
    "[孟岩在《垃圾收集机制批判》](https://blog.csdn.net/myan/article/details/1906)中一针见血地点出智能指针的优势;\"C++ 利用智能指针达成的效果是∶一旦某对象不再被引用，系统刻不容缓，立刻回收内存。这通常发生在关键任务完成后的清理（clean up）时期，不会影响关键任务的实时性，同时，内存里所有的对象都是有用的，绝对没有垃圾空占内存。\""
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 1.7 插曲∶系统地避免各种指针错误\n",
    "\n",
    "我同意孟岩说的大部分用C写的上规模的软件都存在一些内存方面的错误，需要花费大量的精力和时间把产品稳定下来。\"举例来说，就像Nginx这样成熟且广泛使用的 C语言产品都会不时暴露出低级的内存错误。\n",
    "\n",
    "内存方面的问题在C++里很容易解决，我第一次也是最后一次见到别人的代码里有内存泄漏是在 2004年实习那会儿，我自己写的C++程序从来没有出现过内存方面的问题。\n",
    "\n",
    "C++ 里可能出现的内存问题大致有这么几个方面∶\n",
    "- 缓冲区溢出（buffer overrun ）。\n",
    "- 空悬指针/野指针。\n",
    "- 重复释放（double delete）。\n",
    "- 内存泄漏（memory leak）。\n",
    "- 不配对的 new[]/delete。\n",
    "- 内存碎片（memory fragmentation ）。\n",
    "\n",
    "正确使用智能指针能很轻易地解决前面5个问题，解决第 6个问题需要别的思路，我会在§9.2.1和§A.1.8探讨。\n",
    "- 缓冲区溢出∶用`std::vector<char>`/`std::string` 或自己编写 `Buffer class` 来管理缓冲区，自动记住用缓冲区的长度，并通过成员函数而不是裸指针来修改缓冲区。\n",
    "- 空悬指针/野指针∶用 `shared_ptr`/`weak_ptr`，这正是本章的主题。\n",
    "- 重复释放∶用 `scoped_ptr`，只在对象析构的时候释放一次。\n",
    "- 内存泄漏∶用 `scoped_ptr`，对象析构的时候自动释放内存。\n",
    "- 不配对的 `new[]`/`delete`∶ 把 `new[]`统统替换为`std::vector`/`scoped_array`。\n",
    "\n",
    "正确使用上面提到的这几种智能指针并不难，其难度大概比学习使用 `std::vector`/`std::list`这些标准库组件还要小，与 `std::string`差不多，只要花一周的时间去适应它，就能信手拈来。我认为，在现代的C++程序中一般不会出现 `delete` 语句，资源（包括复杂对象本身）都是通过对象（智能指针或容器）来管理的，不需要程序员还为此操心。\n",
    "\n",
    "在这几种错误里边，内存泄漏相对危害性较小，因为它只是借了东西不归还，程序功能在一段时间内还算正常。其他如缓冲区溢出或重复释放等致命错误可能会造成安全性（security 和 data safety）方面的严重后果。\n",
    "\n",
    "需要注意一点∶`scoped_ptr/shared_ptr/weak_ptr`都是值语意，要么是栈上对象，或是其他对象的直接数据成员，或是标准库容器里的元素。几乎不会有下面这种用法∶"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "shared_ptr<Foo>* pFoo = new shared_ptr<Foo>(new Foo); // WRONG semantic"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "还要注意，如果这几种智能指针是对象`x`的数据成员，而它的模板参数`T`是个incomplete类型，那么`x`的析构函数不能是默认的或内联的，必须在.cpp 文件里边显式定义，否则会有编译错或运行错（原因见§10.3.2）。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 1.8 应用到Observer上\n",
    "\n",
    "既然通过`weak_ptr`能探查对象的生死, 那么Observer模式的竞态条件就很容易解决, 只要让Observer保存`weak_ptr<Observer>`即可:\n",
    "\n",
    "- [Observer_safe](../../recipes/thread/test/Observer_safe.cc)\n",
    "\n",
    "就这么简单。前文代码（3）处（p.10的[`x->update();`](../../recipes/thread/test/Observer.cc#L31)）的竞态条件已经弥补了。思考∶如果把[`std::vector<boost::weak_ptr<Observer> > observers_;`](../../recipes/thread/test/Observer_safe.cc#L51)改为`vector<shared_ptr<0bserver>> observers_;`，会有什么后果?"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## * 解决了吗\n",
    "\n",
    "把 `Observer*`替换为 `weak_ptr<Observer>`部分解决了 Observer 模式的线程安全，但还有以下几个疑点。这些问题留到本章§1.14中去探讨，每个都是能解决的。\n",
    "\n",
    "侵入性: \n",
    "- 强制要求 Observer 必须以 `shared_ptr`来管理。\n",
    "\n",
    "不是完全线程安全: \n",
    "- Observer 的析构函数会调用 `subject_->unregister(this)`，万一`subject_`已经不复存在了呢?为了解决它，又要求 `Observable` 本身是用`shared_ptr`管理的，并且 `subject_`多半是个 `weak_ptr<Observable>`。\n",
    "\n",
    "锁争用（lock contention）: \n",
    "- 即 `Observable` 的三个成员函数都用了互斥器来同步，这会造成 `register_()`和 `unregister()`等待`notifyObservers()`，而后者的执行时间是无上限的，因为它同步回调了用户提供的`update()`函数。我们希望`register_()`和 `unregister()`的执行时间不会超过某个固定的上限，以免殃及无辜群众。\n",
    "\n",
    "死锁:\n",
    "- 万一[`obj->update();`](../../recipes/thread/test/Observer_safe.cc#L38)的 `update()`虚函数中调用了`(un)register `呢? 如果 `mutex_`是不可重入的，那么会死锁; 如果`mutex_`是可重入的，程序会面临迭代器失效（core dump是最好的结果），因为 `vector observers_`在遍历期间被意外地修改了。这个问题乍看起来似乎没有解决办法，除非在文档里做要求。（一种办法是∶用可重入的`mutex_`，把容器换为`std::list`，并把`++it`往前挪一行。）\n",
    "\n",
    "我个人倾向于使用不可重人的 `mutex`，例如 `Pthreads` 默认提供的那个，因为\"要求 `mutex`可重入\"本身往往意味着设计上出了问题（§2.1.1）。Java的 `intrinsiclock` 是可重入的，因为要允许 `synchronized` 方法相互调用（派生类调用基类的同名`synchronized方`法），我觉得这也是无奈之举。\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# * 1.9 再论 shared_ptr的线程安全\n",
    "\n",
    "虽然我们借 `shared_ptr` 来实现线程安全的对象释放，但是 `shared_ptr`本身不是100%线程安全的。它的引用计数本身是安全且无锁的，但对象的读写则不是，因为`shared_ptr`有两个数据成员，读写操作不能原子化. 根据文档，`shared_ptr`的线程安全级别和内建类型、标准库容器、`std::string`一样，即∶\n",
    "- 一个 `shared_ptr` 对象实体可被多个线程同时读取;\n",
    "- 两个 `shared_ptr` 对象实体可以被两个线程同时写入，\"析构\"算写操作;\n",
    "- 如果要从多个线程读写同一个 `shared_ptr` 对象，那么需要加锁。\n",
    "\n",
    "请注意，以上是 `shared_ptr` 对象本身的线程安全级别，不是它管理的对象的线程安全级别。\n",
    "\n",
    "要在多个线程中同时访问同一个 `shared_ptr`，正确的做法是用`mutex`保护∶"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "MutexLock mutex;// No need for ReaderWriterLock \n",
    "shared_ptr<Foo> globalPtr;\n",
    "\n",
    "// 我们的任务是把 globalPtr 安全地传给 doit（）\n",
    "void doit(const shared_ptr<Foo>& pFoo);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`globalPtr` 能被多个线程看到，那么它的读写需要加锁。注意我们不必用读写锁，而只用最简单的互斥锁，这是为了性能考虑。因为临界区非常小，用互斥锁也不会阻塞并发读。\n",
    "\n",
    "为了拷贝`globalPtr`, 需要在读取它的时候加锁, 即"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "// 锁的作用时间只覆盖“复制指针”这一步，极短。\n",
    "// 复制后，`localPtr` 是当前线程独享的 `shared_ptr` 实例，可无锁使用。\n",
    "// `Foo` 对象的引用计数在锁内加一，但 `Foo` 本身的访问发生在锁外。\n",
    "\n",
    "void read()\n",
    "{\n",
    "    shared_ptr<Foo> localPtr;\n",
    "    { // 在锁内只做“复制控制块指针”这类 O(1) 操作，锁外使用本地副本，避免长时间持锁\n",
    "        MutexLocalGuard lock(mutex);\n",
    "        localPtr = globalPtr; // read globalPtr\n",
    "    }\n",
    "\n",
    "    // use localPtr since here, 读写localPtr也无需加锁\n",
    "    doit(localPtr);\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "> AI: \n",
    "\n",
    "`localPtr` 是 `shared_ptr` 类型的局部变量，放在当前线程的栈上，只有这个线程能访问，所以是“当前线程独享的 `shared_ptr` 实例”。每个线程都有自己的栈，互相隔离。\n",
    "\n",
    "```asciidoc\n",
    "堆内资源 (共享)\n",
    "           +-----------------+\n",
    "           |     Foo         |\n",
    "           +-----------------+\n",
    "                    ^\n",
    "    +---------------+---------------+\n",
    "    |                               |\n",
    "(需要加锁)                      (各线程栈内独享)\n",
    "globalPtr ----------------------> localPtr / localPtr2 / ...\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "写入的时候也要加锁∶"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "// 错误写法\n",
    "// - 如果 globalPtr 原来指向的对象只有这一份引用，那么在 globalPtr = newPtr; 这句执行后，旧对象的析构函数会立刻执行，而且发生在锁里面。\n",
    "// - 万一旧对象析构里做了复杂操作（比如写文件、发网络请求），锁就被占很久，其他线程都被挡住。\n",
    "void write() \n",
    "{\n",
    "    shared_ptr<Foo> newPtr(new Foo); // 注意，对象的创建在临界区之外\n",
    "    {\n",
    "        MutexLockGuard lock(mutex);\n",
    "        globalPtr = newPtr; // write to globalPtr; 这里可能在锁里面把旧对象销毁\n",
    "    }\n",
    "    \n",
    "    // use newPtr since here，读写 newPtr 无须加锁\n",
    "    doit(newPtr);\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "注意到上面的`read()`和 `write()`在临界区之外都没有再访问`globalPtr`，而是用了一个指向同一`Foo`对象的栈上 `shared_ptr local copy`. 下面会谈到，只要有这样的local copy存在，`shared_ptr`作为函数参数传递时不必复制，用reference to const作为参数类型即可。\n",
    "\n",
    "另外注意到上面的`new Foo`是在临界区之外执行的，这种写法通常比在临界区内写`globalPtr.reset(new Foo)`要好，因为缩短了临界区长度。如果要销毁对象，我们固然可以在临界区内执行`globalPtr.reset()`，但是这样往往会让对象析构发生在临界区以内，增加了临界区的长度。\n",
    "\n",
    "一种改进办法是像上面一样定义一个`localPtr`，用它在临界区内与`globalPtr`交换（`swap()`），这样能保证把对象的销毁推迟到临界区之外。\n",
    "\n",
    "- 练习∶在 `write()`函数中，`globalPtr = newPtr;`这一句有可能会在临界区内销毁原来 `globalPtr` 指向的 `Foo` 对象，设法将销毁行为移出临界区。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "// 新对象的构造(`make_shared`) 在进入锁之前完成，不阻塞其他线程。\n",
    "// 锁内操作：\n",
    "// - 将旧的 `globalPtr` 移动给 `oldPtr`（不增加引用计数）。\n",
    "// - 将新对象指针写回 `globalPtr`。\n",
    "// - 持锁时间仅包含快速的指针操作。\n",
    "// 锁释放后：\n",
    "// - 如果旧对象没有其他引用，析构在锁外进行，避免拖慢临界区。\n",
    "// - 新对象可放心使用。\n",
    "\n",
    "void write() {\n",
    "    auto newPtr = std::make_shared<Foo>(); // 先在锁外创建新对象\n",
    "    std::shared_ptr<Foo> oldPtr;           // 准备接管旧对象\n",
    "\n",
    "    { // 持锁时间仅包含快速的指针操作\n",
    "        MutexLockGuard lock(mutex);\n",
    "        oldPtr = std::move(globalPtr);     // 把旧的“遥控器”拿出来\n",
    "        globalPtr = newPtr;                // 放入新的\n",
    "    } // 锁释放; 如果旧对象没有其他引用，析构在锁外进行，避免拖慢临界区\n",
    "\n",
    "    // 到这儿，锁已经没有人用了\n",
    "    // 如果 oldPtr 是旧对象最后一个遥控器，它会在这里析构，锁外安全\n",
    "    doit(newPtr);\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 1.10 `shared_ptr` 技术与陷阱\n",
    "\n",
    "## 意外延长对象的生命期\n",
    "\n",
    "`shared_ptr` 是强引用（\"铁丝\"绑的），只要有一个指向`x`对象的 `shared_ptr` 存在，该对象就不会析构。而 `shared_ptr` 又是允许拷贝构造和赋值的（否则引用计数就无意义了），如果不小心遗留了一个拷贝，那么对象就永世长存了。\n",
    "\n",
    "例如前面提到如果把p.16中 [Observer_safe](../../recipes/thread/test/Observer_safe.cc#L51)的类型改为`vector<shared_ptr<Observer>>`，那么除非手动调用 `unregister()`，否则 `Observer` 对象永远不会析构。即便它的析构函数会调用 `unregister()`，但是不去`unregister()`就不会调用`Observer` 的析构函数，这变成了鸡与蛋的问题。这也是Java 内存泄漏的常见原因。\n",
    "\n",
    "另外一个出错的可能是 `boost::bind`，因为 `boost::bind` 会把实参拷贝一份，如果参数是个 `shared_ptr`，那么对象的生命期就不会短于`boost::function`对象∶"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "class Foo\n",
    "{\n",
    "\tvoid doit();\n",
    "};\n",
    "\n",
    "shared_ptr<Foo> pFoo(new Foo);\n",
    "boost::function<void()> func = boost::bind(&Foo::doit, pFoo); // long life too"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "这里 `func` 对象持有了 `shared_ptr<Foo>`的一份拷贝，有可能会在不经意间延长倒数第二行创建的 `Foo` 对象的生命期。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 函数参数\n",
    "\n",
    "为要修改引用计数（而且拷贝的时候通常要加锁）、`shared_ptr`的拷贝开销比拷贝原始指针要高，但是需要拷贝的时候并不多。多数情况下它可以以const reference 方式传递，一个线程只需要在最外层函数有一个实体对象，之后都可以用const reference来使用这个 `shared_ptr`。\n",
    "\n",
    "例如有几个函数都要用到 `Foo` 对象∶"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "void save(const shared_ptr<Foo>& pFoo); // pass by const reference\n",
    "\n",
    "void validate(const Foo& foo);\n",
    "\n",
    "bool validate(const shared_ptr<Foo>& pFoo) // pass by const reference\n",
    "{\n",
    "    validateAccount(*pFoo);\n",
    "    // ...\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "那么在通常情况下，我们可以传常引用（pass by const reference）∶"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "void onMessage(const string& msg)\n",
    "{\n",
    "    shared_ptr<Foo> pFoo(new Foo(msg)); // 只要在最外层只有一个实体, 安全不成问题\n",
    "    if(validate(pFoo)) // 没有拷贝pFoo\n",
    "    {\n",
    "        save(pFoo); // 没有拷贝pFoo\n",
    "    }\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "遵照这个规则，基本上不会遇到反复拷贝 `shared_ptr` 导致的性能问题。另外由于 `pFoo` 是栈上对象，不可能被别的线程看到，那么读取始终是线程安全的。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "> AI:\n",
    "\n",
    "- `pFoo` 是 `onMessage` 函数内的局部变量，作用域仅限当前线程。\n",
    "- “最外层只有一个实体”指的就是：在这个函数中，只有这一份名为 `pFoo` 的 `shared_ptr` 对象实体存在，其他线程看不到、也不会同时修改它。\n",
    "- 由于该实体不暴露到其他线程，**不存在对同一个 `shared_ptr` 对象实体的并发读写**，因此不涉及线程安全问题。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 析构动作在创建时被捕获\n",
    "\n",
    "这是一个非常有用的特性，这意味着;\n",
    "\n",
    "- 虚析构不再是必需的。\n",
    "- `shared_ptr<void>`可以持有任何对象，而且能安全地释放。\n",
    "- `shared_ptr`对象可以安全地跨越模块边界，比如从 DLL 里返回，而不会造成从模块 A分配的内存在模块 B里被释放这种错误。\n",
    "- 二进制兼容性，即便 `Foo` 对象的大小变了，那么旧的客户代码仍然可以使用新的动态库，而无须重新编译。前提是 `Foo` 的头文件中不出现访问对象的成员的inline 函数，并且 `Foo` 对象的由动态库中的 Factory 构造，返回其 `shared_ptr`。\n",
    "- 析构动作可以定制。\n",
    "\n",
    "最后这个特性的实现比较巧妙，因为`shared_ptr<T>`只有一个模板参数，而\"析构行为\"可以是函数指针、仿函数（functor）或者其他什么东西。这是泛型编程和面向对象编程的一次完美结合。有兴趣的读者可以参考 Scott Mevers 的[文章](https://www.artima.com/articles/my-most-important-c-aha-momentsemeverem)。这个技术在后面的对象池中还会用到。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "auto p = std::shared_ptr<void>(rawPtr, customDeleter); // 额外提供一个“删除器”对象"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "> AI:\n",
    "\n",
    "**不必一定依赖虚析构函数**\n",
    "- 因为释放操作可以由删除器控制，即便类没有声明虚析构，也能安全删除。\n",
    "\n",
    "（当然，如果你打算通过基类指针 `delete` 派生对象，仍需要虚析构；这里只说 `shared_ptr` 持有时可以规避。）\n",
    "\n",
    "**`shared_ptr<void>` 也能安全使用**\n",
    "- `shared_ptr<void>` 常用于不关心静态类型的场景。只要在创建时捕获到正确的删除器（比如 `delete Derived` 或自定义释放逻辑），即使静态类型是 `void`，仍能正确释放真实对象。\n",
    "\n",
    "**跨模块（DLL/so）边界安全**\n",
    "- 内存在哪个模块里分配、在哪个模块里释放是个大麻烦。通过自定义删除器，我们可以保证释放工作在分配它的同一模块环境中完成，从而避免“模块 A 分配、模块 B delete”导致的崩溃。\n",
    "\n",
    "**支持二进制兼容更新**\n",
    "- 假设 `Foo` 类在新版动态库中增减了字段，只要：\n",
    "  - 客户端只通过接口拿 `shared_ptr<Foo>`；\n",
    "  - 客户端不内联访问成员；\n",
    "  - 对象由动态库的工厂函数创建并返回 `shared_ptr`；\n",
    "\n",
    "那么旧客户端无需重新编译也能安全使用新库。控制块里捕获的删除器仍指向正确的析构逻辑。\n",
    "\n",
    "**析构动作可以高度定制**\n",
    "- 不仅可以调用 `delete`，还可以调用自定义函数：释放文件句柄、归还对象池、解除注册等等。控制块里保存的删除器可以是任意可调用对象，灵活强大。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 析构所在的线程\n",
    "\n",
    "对象的析构是同步的，当最后一个指向`x`的 `shared_ptr` 离开其作用域的时候，`x`会同时在同一个线程析构。这个线程不一定是对象诞生的线程。\n",
    "\n",
    "这个特性是把双刃剑∶如果对象的析构比较耗时，那么可能会拖慢关键线程的速度（如果最后一个`shared_ptr` 引发的析构发生在关键线程）; 同时，我们可以用一个单独的线程来专门做析构，通过一个`BlockingQueue<shared_ptr<void>>`把对象的析构都转移到那个专用线程，从而解放关键线程。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 现成的 RAII handle\n",
    "\n",
    "我认为 RAII（资源获取即初始化）是C++语言区别于其他所有编程语言的最重要的特性，一个不懂RAII的C++程序员不是一个合格的C++程序员。\n",
    "\n",
    "初学C++的教条是\"`new`和`delete`要配对，`new`了之后要记着`delete`\"; 如果使用 RAII[CCS，条款13]，要改成\"每一个明确的资源配置动作（例如 `new`）都应该在单一语句中执行，并在该语句中立刻将配置获得的资源交给 `handle` 对象（如`shared_ptr`），程序中一般不出现 `delete`\"\n",
    "\n",
    "`shared_ptr`是管理共享资源的利器，需要注意避免循环引用，通常的做法是 `owner`持有指向 `child`的 `shared_ptr`，`child`持有指向 `owner` 的 `weak_ptr`"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 1.11 对象池\n",
    "\n",
    "假设有 `Stock`类，代表一只股票的价格。每一只股票有一个唯一的字符串标识，比如 Google 的key是\"NASDAQ∶G00G\"，IBM是\"NYSE∶IBM\"。`Stock` 对象是个主动对象，它能不断获取新价格。为了节省系统资源，同一个程序里边每一只出现的股票只有一个`Stock `对象，如果多处用到同一只股票，那么 `Stock`对象应该被共享。如果某一只股票没有再在任何地方用到，其对应的Stock 对象应该析构，以释放资源，这隐含了\"引用计数\"。\n",
    "\n",
    "为了达到上述要求，我们可以设计一个对象池 [`StockFactory`](../../recipes/thread/test/Factory.cc)。它的接口很简单，根据 `key` 返回`Stock` 对象。我们已经知道，在多线程程序中，既然对象可能被销毁，那么返回 `shared_ptr`是合理的。自然地，我们写出如下代码(可惜是错的)。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "// version 1: questionable code\n",
    "class StockFactory: boost::noncopyable\n",
    "{\n",
    "public: \n",
    "\t\tshared_ptr<Stock> get(const string& key);\n",
    "\n",
    "private: \n",
    "\t\tmutable MutexLock mutex_;\n",
    "    std::map<string, shared_ptr<Stock>> stocks_;\n",
    "};"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`get()`的逻辑很简单，如果在`stocks_`里找到了 `key`，就返回`stocks_[key]`; 否则新建一个 `Stock`，并存入 `stocks_[key]`。\n",
    "\n",
    "细心的读者或许已经发现这里有一个问题，`Stock` 对象永远不会被销毁，因为`map`里存的是 `shared_ptr`，始终有\"铁丝\"绑着。那么或许应该仿照前面`Observable`那样存一个 `weak_ptr`?\n",
    "\n",
    "比如"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "// version 2∶数据成员修改为 std::map<string，weak_ptr<Stock>> stocks_;\n",
    "\n",
    "class StockFactory: boost::noncopyable\n",
    "{\n",
    "public: \n",
    "\t\tshared_ptr<Stock> get(const string& key);\n",
    "\n",
    "private: \n",
    "\t\tmutable MutexLock mutex_;\n",
    "    std::map<string, weak_ptr<Stock>> stocks_;\n",
    "};\n",
    "\n",
    "shared_ptr<Stock> StockFactory::get(const string& key)\n",
    "{\n",
    "    shared_ptr<Stock> pStock;\n",
    "    MutexLockGuard lock(mutex_);\n",
    "\n",
    "    weak_ptr<Stock>& wkStock = stocks_[key]; // 如果 key 不存在，会默认构造一个\n",
    "    pStock = wkStock.lock(); // 尝试把\"棉线\"提升为\"铁丝'\n",
    "    if(!pStock){\n",
    "        pStock.reset(new Stock(key));\n",
    "        wkStock = pStock; // 这里更新了 stocks_[key]，注意 wkStock 是个引用\n",
    "    }\n",
    "    return pStock;\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "这么做固然 `Stock` 对象是销毁了，但是程序却出现了轻微的内存泄漏，为什么?\n",
    "\n",
    "因为`stocks_`的大小只增不减，`stocks_.size()`是曾经存活过的 `Stock`对象的总数，即便活的 `Stock` 对象数目降为`0`。或许有人认为这不算泄漏，因为内存并不是彻底遗失不能访问了，而是被某个标准库容器占用了。我认为这也算内存泄漏，毕竟是\"战场\"没有打扫干净。\n",
    "\n",
    "其实，考虑到世界上的股票数目是有限的，这个内存不会一直泄漏下去，大不了把每只股票的对象都创建一遍。估计泄漏的内存也只有几兆字节。如果这是一个其他类型的对象池，对象的 `key` 的集合不是封闭的，内存就会一直泄漏下去。\n",
    "\n",
    "解决的办法是，利用`shared_ptr`的定制析构功能。`shared_ptr`的构造函数可以有一个额外的模板类型参数，传入一个函数指针或仿函数`d`，在析构对象时执行`d(ptr)`，其中 `ptr`是 `shared_ptr` 保存的对象指针。`shared_ptr`这么设计并不是多余的，因为反正要在创建对象时捕获释放动作，始终需要一个 bridge。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "template <class Y, class D> shared_ptr::shared_ptr(Y* p, D d);\n",
    "template <class Y, class D> void shared_ptr::reset(Y* p, D d);\n",
    "// 注意 Y的类型可能与 T 不同，这是合法的，只要Y*能隐式转换为T*。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "那么我们可以利用这一点，在析构 `Stock` 对象的同时清理`stocks_`。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "// version 3 \n",
    "\n",
    "class StockFactory: boost::noncopyable\n",
    "{\n",
    "// 在get()中，将pStock.reset(new Stock(key); 改为:\n",
    "// pStock.reset(new Stock(key), \n",
    "//              boost::bind(&StockFactory::deleteStock, this, _1)); // ***\n",
    "\n",
    "private: \n",
    "void deleteStock(Stock* stock)\n",
    "{\n",
    "    if(stock){\n",
    "      MutexLockGuard lock(mutex_);\n",
    "      stocks_.erase(stock->key());\n",
    "    }\n",
    "    delete stock // sorry,I lied\n",
    "}\n",
    "\n",
    "// assuming StockFactory lives longer than all Stock's ...\n",
    "// ...\n",
    "};"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "这里我们向`pStock.reset()`传递了第二个参数，一个`boost::function`，让它在析构 `Stock* p`时调用本 `StockFactory` 对象的` deleteStock` 成员函数。\n",
    "\n",
    "警惕的读者可能已经发现问题，那就是我们把一个原始的`StockFactory this`指针保存在了`boost::function`里（`***`处），这会有线程安全问题。如果这个 `StockFactory`先于`Stock`对象析构，那么会 core dump。正如 `Observer`在析构函数里去调用`Observable::unregister()`，而那时 `Observable` 对象可能已经不存在了。\n",
    "\n",
    "当然这也是能解决的，要用到§1.11.2介绍的弱回调技术。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## enable_shared_from_this\n",
    "\n",
    "`StockFactory::get()`把原始指针 `this` 保存到了`boost::function`中（`***`处），如果 `StockFactory`的生命期比 `Stock` 短，那么 `Stock` 析构时去回调 `StockFactory::deleteStock` 就会core dump。似乎我们应该祭出惯用的 `shared_ptr` 大法来解决对象生命期问题，但是`StockFactory::get()`本身是个成员函数，如何获得一个指向当前对象的 `shared_ptr<StockFactory>`对象呢?\n",
    "\n",
    "有办法，用`enable_shared_from_this`。这是一个以其派生类为模板类型实参的基类模板，继承它，`this`指针就能变身为`shared_ptr`。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "class StockFactory : public boost::noncopyable,\n",
    "                     boost::enable_shared_from_this<StockFactory>\n",
    "{\n",
    "  // ...\n",
    "};"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "为了使用`shared_from_this()`，`StockFactory`不能是`stack object`，必须是 `heap object`且由 `shared_ptr`管理其生命期，即∶"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "shared_ptr<StockFactory> stockFactory(new StockFactory);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "万事俱备，可以让`this` 摇身一变，化为`shared_ptr<StockFactory>`了。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "// version 4\n",
    "shared_ptr<Stock> StockFactory::get(const string& key)\n",
    "{\n",
    "    // change pStock.reset(new Stock(key), boost::bind(&StockFactory::deleteStock, this, _1)) to\n",
    "    pStock.reset(new Stock(key),\n",
    "                boost::bind(&StockFactory::deleteStock,\n",
    "                shared_from_this(),\n",
    "                _1));\n",
    "    // ...\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "这样一来，`boost::function` 里保存了一份 `shared_ptr<StockFactory>`，可以保证调用 `StockFactory::deleteStock` 的时候那个`StockFactory` 对象还活着。\n",
    "\n",
    "注意一点，`shared_from_this()`能在构造函数里调用，因为在构造 `StockFactory` 的时候，它还没有被交给 `shared_ptr`接管。\n",
    "\n",
    "最后一个问题，`StockFactory` 的生命期似乎被意外延长了。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 弱回调\n",
    "\n",
    "把 `shared_ptr`绑（`boost::bind`）到 `boost::function`里，那么回调的时候`StockFactory` 对象始终存在，是安全的。这同时也延长了对象的生命期，使之不短于绑得的 `boost::function` 对象。\n",
    "\n",
    "有时候我们需要\"如果对象还活着，就调用它的成员函数，否则忽略之\"的语意，就像 `Observable::notifyObservers()`那样，我称之为\"弱回调\"。这也是可以实现的，利用 `weak_ptr`，我们可以把 `weak_ptr`绑到 `boost::function` 里，这样对象的生命期就不会被延长。然后在回调的时候先尝试提升为 `shared_ptr`，如果提升成功，说明接受回调的对象还健在，那么就执行回调;如果提升失败，就不必劳神了。\n",
    "\n",
    "使用这一技术的完整`StockFactory`代码如下\n",
    "- [`StockFactory`](../../recipes/thread/test/Factory.cc#L172)\n",
    "\n",
    "这下完美了，无论 `Stock` 和 `StockFactory`谁先挂掉都不会影响程序的正确运行。这里我们借助`shared_ptr` 和 `weak_ptr`完美地解决了两个对象相互引用的问题。\n",
    "\n",
    "当然，通常 `Factory` 对象是个singleton，在程序正常运行期间不会销毁，这里只是为了展示弱回调技术 ，这个技术在事件通知中非常有用。\n",
    "\n",
    "> 通用的弱回调封装见 recipes/head/NeakCallback.h，用到了C++11的variadic template和rvalue reference\n",
    "\n",
    "本节的 `StockFactory`只有针对单个 `Stock` 对象的操作，如果程序需要遍历整个`stocks_`，稍不注意就会造成死锁或数据损坏（§2.1），请参考§2.8的解决办法。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 1.12 替代方案\t\n",
    "\n",
    "除了使用 `shared_ptr/weak_ptr`，要想在C++里做到线程安全的对象回调与析构，可能的办法有以下一些。\n",
    "\n",
    "1. 用一个全局的 facade 来代理 `Foo` 类型对象访问，所有的 `Foo` 对象回调和析构都通过这个 facade 来做，也就是把指针替换为 `objId/handle`，每次要调用对象的成员函数的时候先 check-out，用完之后再check-in 。这样理论上能避免 race condition，但是代价很大。因为要想把这个 facade 做成线程安全的，那么必然要用互斥锁。这样一来，从两个线程访问两个不同的`Foo` 对象也会用到同-个锁，让本来能够并行执行的函数变成了串行执行，没能发挥多核的优势。当然，可以像Java 的 ConcurrentHashMap 那样用多个buckets，每个bucket分别加锁，以降低 contention。\n",
    "> 这是Jeff Grossman 在《[A technique for safe deletion with object locking](https://dl.acm.org/doi/10.5555/331120.331197)》一文中提出的办法 \n",
    "2. §1.4提到的\"只创建不销毁\"手法，实属无奈之举。\n",
    "3. [自己编写引用计数的智能指针](https://blog.csdn.net/solstice/article/details/5238671)。本质上是重新发明轮子，把 `shared_ptr`实现一遍。正确实现线程安全的引用计数智能指针不是一件容易的事情，而高效的实现就更加困难。既然 `shared_ptr` 已经提供了完整的解决方案，那么似乎没有理由抗拒它。\n",
    "4. 将来在C++11里有 `unique_ptr`，能避免引用计数的开销，或许能在某些场合替换 `shared_ptr`"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 其他语言怎么办\n",
    "\n",
    "有垃圾回收就好办。Google 的 Go语言教程明确指出，没有垃圾回收的并发编程是困难的（Concurrency is hard without garbage collection）。但是由于指针算术的存在，在 C/C++ 里实现全自动垃圾回收更加困难。而那些天生具备垃圾回收的语言在并发编程方面具有明显的优势，Java是目前支持并发编程最好的主流语言，它的util.concurrent 库和内存模型是C++11效仿的对象。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 1.13 心得与小结\n",
    "\n",
    "学习多线程程序设计远远不是看看教程了解 API怎么用那么简单，这最多\"主要是为了读懂别人的代码，如果自己要写这类代码，必须专门花时间严肃、认真、系统地学习，严禁半桶水上阵\"[（孟岩）](https://blog.csdn.net/myan/article/details/3144661)\n",
    "\n",
    "一般的多线程教程上都会提到要让加锁的区域足够小，这没错，问题是如何找出这样的区域并加锁，本章S1.9举的安全读写`shared_ptr` 可算是一个例子。\n",
    "\n",
    "据我所知，目前C++没有特别好的多线程领域专著，但C语言有，Java 语言也有。《Java Concurrency in Practice》[JCP]是我读过的写得最好的书，内容足够新，可读性和可操作性俱佳。C++程序员反过来要向 Java学习，多少有些讽刺。除了编程书，操作系统教材也是必读的，至少要完整地学习一本经典教材的相关章节，可从《操作系统设计与实现》、《现代操作系统》、《操作系统概念》任选本，了解各种同步原语、临界区、竞态条件、死锁、典型的IPC 问题等等，防止闭门造车。\n",
    "\n",
    "分析可能出现的 race condition 不仅是多线程编程的基本功，也是设计分布式系统的基本功，需要反复历练，形成一定的思考范式，并积累一些经验教训，才能少犯错误。这是一个快速发展的领域，要不断吸收新知识，才不会落伍。单CPU时代的多线程编程经验到了多CPU时代不一定有效，因为多 CPU能做到真正的并行执行，每个CPU看到的事件发生顺序不一定完全相同。正如狭义相对论所说的每个观察者都有自己的时钟，在不违反因果律的前提下，可能发生十分违反直觉的事情。\n",
    "\n",
    "尽管本章通篇在讲如何安全地使用（包括析构）跨线程的对象，但我建议尽量减少使用跨线程的对象，我赞同水木网友 ilovecpp 说的∶\"用流水线，生产者消费者，任务队列这些有规律的机制，最低限度地共享数据。这是我所知最好的多线程编程的建议了。\"\n",
    "\n",
    "不用跨线程的对象，自然不会遇到本章描述的各种险态。如果迫不得已要用，希望本章内容能对你有帮助。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 小结\n",
    "\n",
    "- 原始指针暴露给多个线程往往会造成 race condition 或额外的簿记负担。\n",
    "- 统一用 `shared_ptr/scoped_ptr`来管理对象的生命期，在多线程中尤其重要。\n",
    "- `shared_ptr` 是值语意，当心意外延长对象的生命期。例如 `boost::bind`和容器都可能拷贝 s`hared_ptr`。\n",
    "- `weak_ptr`是`shared_ptr` 的好搭档，可以用作弱回调、对象池等。\n",
    "- 认真阅读一遍 `boost::shared_ptr`的文档，能学到很多东西\n",
    "- 保持开放心态，留意更好的解决办法，比如C++11引入的 u`nique_ptr`。忘掉已被废弃的 `auto_ptr`。\n",
    "\n",
    "`shared_ptr` 是TR1的一部分，即C++标准库的一部分，值得花一点时间去[学习掌握](https://blog.csdn.net/myan/article/details/1906)，对编写现代的C++程序有莫大的帮助。我个人的经验是，一周左右就能基本掌握各种用法与常见陷阱，比学 STL 还快。网络上有一些对 `shared_ptr` 的批评，那可以算作故意误用的例子，就好比故意访问失效的迭代器来证明`std::vector`不安全一样。\n",
    "\n",
    "正确使用标准库（含 `shared_ptr`）作为自动化的内存/资源管理器，解放大脑，从此告别内存错误。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 1.14 Observer之谬\n",
    "\n",
    "本章§1.8把 `shared_ptr/weak_ptr` 应用到 `Observer`模式中，部分解决了其线程安全问题。我用`Observer`举例，因为这是一个广为人知的设计模式，但是它有本质的问题。\n",
    "\n",
    "`Observer` 模式的本质问题在于其面向对象的设计。换句话说，我认为正是面向对象（OO）本身造成了 `Observer` 的缺点。`Observer` 是基类，这带来了非常强的耦合，强度仅次于友元（friend）。这种耦合不仅限制了成员函数的名字、参数、返回值，还限制了成员函数所属的类型（必须是 `Observer` 的派生类）。\n",
    "\n",
    "`Observer class`是基类，这意味着如果 `Foo` 想要观察两个类型的事件（比如时钟和温度），需要使用多继承。这还不是最糟糕的，如果要重复观察同一类型的事件 （比如1秒一次的心跳和30秒一次的自检），就要用到一些伎俩来 work around，因为不能从一个`Base class`继承两次。\n",
    "\n",
    "现在的语言一般可以绕过`Observer` 模式的限制，比如 Java 可以用匿名内部类， Java8用Closure，C#用 delegate，C++用`boost::function / boost::bind`。\n",
    "\n",
    "在C++里为了替换 `Observer`，可以用 Signal/Slots，我指的不是QT那种靠语言扩展的实现，而是完全靠标准库实现的 thread safe、race condition free、threadcontention free 的 Signal/Slots，并且不强制要求 `shared_ptr`来管理对象，也就是说完全解决了S1.8列出的 `Observer` 遗留问题。这会用到S2.8介绍的\"借 `shared_ptr`实现copy-on-write\"技术。\n",
    "\n",
    "在C++11中，借助 variadic template，实现最简单（trivial）的一对多回调可谓不费吹灰之力，代码如下。\n",
    "\n",
    "- [SignalTrivial](../../recipes/thread/SignalSlotTrivial.h)\n",
    "\n",
    "我们不难把以上基本实现扩展为线程安全的 Signal/Slots，并且在 Slot 析构时自动 unregister。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "#include <functional>\n",
    "#include <vector>\n",
    "\n",
    "// 信号类模板声明：用于特化不同函数签名\n",
    "template<typename Signature>\n",
    "class SignalTrivial;\n",
    "\n",
    "// 针对函数类型 RET(ARGS...) 的特化\n",
    "template <typename RET, typename... ARGS>\n",
    "class SignalTrivial<RET(ARGS...)>\n",
    "{\n",
    " public:\n",
    "  // Functor 是一个可调用对象，接受参数 ARGS...，不关心返回值（忽略 RET）\n",
    "  using Functor = std::function<void (ARGS...)>;\n",
    "\n",
    "  // 注册槽函数：使用右值引用接收，可完美转发 Functor\n",
    "  void connect(Functor&& func)\n",
    "  {\n",
    "    functors_.push_back(std::forward<Functor>(func));\n",
    "  }\n",
    "\n",
    "  // 触发信号：依次调用所有已注册的槽函数\n",
    "  void call(ARGS&&... args)\n",
    "  {\n",
    "    // gcc 4.6 支持范围 for，这里保持旧写法\n",
    "    typename std::vector<Functor>::iterator it = functors_.begin();\n",
    "    for (; it != functors_.end(); ++it)\n",
    "    {\n",
    "      (*it)(args...);  // 调用槽函数，参数按值传递\n",
    "    }\n",
    "  }\n",
    "\n",
    " private:\n",
    "  // 存储槽函数的容器\n",
    "  std::vector<Functor> functors_;\n",
    "};"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "freeFunction 收到: 42\n",
      "lambda 收到: 84\n"
     ]
    }
   ],
   "source": [
    "#include <iostream>\n",
    "\n",
    "void freeFunction(int value)\n",
    "{\n",
    "  std::cout << \"freeFunction 收到: \" << value << '\\n';\n",
    "}\n",
    "\n",
    "{\n",
    "  SignalTrivial<void(int)> signal;\n",
    "\n",
    "  // 连接普通函数\n",
    "  signal.connect(freeFunction);\n",
    "\n",
    "  // 连接 lambda\n",
    "  signal.connect([](int value) {\n",
    "    std::cout << \"lambda 收到: \" << value * 2 << '\\n';\n",
    "  });\n",
    "\n",
    "  // 触发信号，所有已连接的槽函数都会被调用\n",
    "  signal.call(42);\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 结语\n",
    "\n",
    "《C++沉思录》（Ruminations on C++中文版）的附录是王曦和孟岩对作者夫妇二人的采访，在被问到\"请给我们三个你们认为最重要的建议\"时，Koenig 和 Moo 的第一个建议是\"避免使用指针\"。我 2003年读到这段时，理解不深，觉得固然使用指针容易造成内存方面的问题，但是完全不用也是做不到的，毕竟C++的多态要通过指针或引用来起效。6年之后重新拾起来，发现大师的观点何其深刻，不免掩卷长叹。\n",
    "\n",
    "这本书详细地介绍了`handle / body idiom`，这是编写大型C++程序的必备技术，也是实现物理隔离的\"法宝\"，值得细读。\n",
    "\n",
    "目前来看，用 `shared_ptr`来管理资源在国内C++界似乎并不是一种主流做法，很多人排斥智能指针，视其为\"洪水猛兽\"（这或许受了 `auto_ptr`的垃圾设计的影响）。据我所知，很多C++项目还是手动管理内存和资源，因此我觉得有必要把我认为好的做法分享出来，让更多的人尝试并采纳。我觉得 `shared_ptr` 对于编写线程安全的C++程序是至关重要的，不然就得\"土法炼钢\"，自己\"重新发明轮子\"。这让我想起了2001 年前后STL刚刚传入国内，大家也是很犹豫，觉得它性能不高，使用不便，还不如自己造的容器类。10年过去了。现在STL已经是主流。大家也适应了迭代器、容器、算法、适配器、仿函数这些\"新\"名词、\"新\"技术，开始在项目中普遍使用（至少用vector代替数组嘛）。我希望，几年之后人们回头看本章内容，觉得\"怎么讲的都是常识\"，那我的写作目的也就达到了。\n"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "C++17",
   "language": "C++17",
   "name": "xcpp17"
  },
  "language_info": {
   "codemirror_mode": "text/x-c++src",
   "file_extension": ".cpp",
   "mimetype": "text/x-c++src",
   "name": "c++",
   "version": "17"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
